#include "src/net/tcp_client.h"

#include <cstdio>

#include "src/base/fio66_logger.h"
#include "src/net/connector.h"
#include "src/net/event_loop.h"
#include "src/net/sockets_opt.h"

namespace fio66::net {
namespace detail {
void RemoveConnection(EventLoop* loop, const TcpConnectionPtr& conn) {  loop->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
}

void RemoveConnector(const ConnectorPtr& connector)
{
  //connector->
}
}  // namespace detail

TcpClient::TcpClient(EventLoop* loop,
                     const InetAddress& serverAddr,
                     const std::string& nameArg)
  : loop_(CHECK_NOTNULL(loop)),
    connector_(new Connector(loop, serverAddr)),
    name_(nameArg),
    connection_callback_(DefaultConnectionCallback),
    message_callback_(DefaultMessageCallback),
    retry_(false),
    connect_(true),
    next_conn_id_(1)
{
  connector_->SetNewConnectionCallback(
      std::bind(&TcpClient::NewConnection, this, _1));
  // FIXME setConnectFailedCallback
  LOGI << "TcpClient::TcpClient[" << name_
           << "] - connector " << get_pointer(connector_);
}

TcpClient::~TcpClient()
{
  LOGI << "TcpClient::~TcpClient[" << name_
           << "] - connector " << get_pointer(connector_);
  TcpConnectionPtr conn;
  bool unique = false;
  {
    std::lock_guard lock(mutex_);
    unique = connection_.unique();
    conn = connection_;
  }
  if (conn)
  {
    assert(loop_ == conn->GetLoop());
    // FIXME: not 100% safe, if we are in different thread
    CloseCallback cb = std::bind(&detail::RemoveConnection, loop_, _1);
    loop_->RunInLoop(
        std::bind(&TcpConnection::SetCloseCallback, conn, cb));
    if (unique)
    {
      conn->ForceClose();
    }
  }
  else
  {
    connector_->Stop();
    // FIXME: HACK
    loop_->RunAfter(1, std::bind(&detail::RemoveConnector, connector_));
  }
}

void TcpClient::Connect()
{
  // FIXME: check state
  LOGI << "TcpClient::Connect[" << name_ << "] - connecting to "
           << connector_->ServerAddress().ToIpPort();
  connect_ = true;
  connector_->Start();
}

void TcpClient::Disconnect()
{
  connect_ = false;

  {
    std::lock_guard lock(mutex_);
    if (connection_)
    {
      connection_->Shutdown();
    }
  }
}

void TcpClient::Stop()
{
  connect_ = false;
  connector_->Stop();
}

void TcpClient::NewConnection(int sockfd)
{
  loop_->AssertInLoopThread();
  InetAddress peerAddr(sockets::GetPeerAddr(sockfd));
  char buf[32];
  snprintf(buf, sizeof buf, ":%s#%d", peerAddr.ToIpPort().c_str(), next_conn_id_);
  ++next_conn_id_;
  std::string connName = name_ + buf;

  InetAddress localAddr(sockets::GetLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new Connection
  // FIXME use make_shared if necessary
  TcpConnectionPtr conn(new TcpConnection(loop_,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));

  conn->SetConnectionCallback(connection_callback_);
  conn->SetMessageCallback(message_callback_);
  conn->SetWriteCompleteCallback(write_complete_callback_);
  conn->SetCloseCallback(
      std::bind(&TcpClient::RemoveConnection, this, _1)); // FIXME: unsafe
  {
    std::lock_guard lock(mutex_);
    connection_ = conn;
  }
  conn->ConnectEstablished();
}

void TcpClient::RemoveConnection(const TcpConnectionPtr& conn)
{
  loop_->AssertInLoopThread();
  assert(loop_ == conn->GetLoop());

  {
    std::lock_guard lock(mutex_);
    assert(connection_ == conn);
    connection_.reset();
  }

  loop_->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
  if (retry_ && connect_)
  {
    LOGI << "TcpClient::Connect[" << name_ << "] - Reconnecting to "
             << connector_->ServerAddress().ToIpPort();
    connector_->Restart();
  }
}


}  // namespace fio66::net
